package com.example.aop;

import com.example.annotation.NoRepeat;
import com.example.utils.RedisUtils;
import com.example.utils.SpELUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class NoRepeatAspect {

    private final RedisUtils redisUtils;

    public NoRepeatAspect(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    @Around("@annotation(com.example.annotation.NoRepeat)")
    public Object noRepeat(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取请求参数
        Object[] args = joinPoint.getArgs();
        // 获取请求方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 获取注解信息
        NoRepeat noRepeat = method.getAnnotation(NoRepeat.class);
        String redisKey = noRepeat.redisKey();
        //获取分布式锁的key,动态参数注解传入,是参数的字符串拼接,自定义传入
        if(!redisKey.isEmpty()){
            redisKey = SpELUtil.generateKeyBySpEL(redisKey, joinPoint);
        }
        String key = redisKey.isEmpty() ? getRedisKey(joinPoint) : redisKey;

        // 判断是否已经请求过
        if (Boolean.TRUE.equals(redisUtils.hasKey(key))) {
            System.out.println("请勿重复提交");
            return "请勿重复提交";
        }
        //标记key请求已经处理过,多线程并发问题验证是否重复提交
        boolean lock = redisUtils.setNx(redisKey, "1", noRepeat.expire(), noRepeat.timeUnit());
        if(!lock){
            //返回false说明分布式锁已设置过,
            System.out.println("请勿重复提交信息,分布式锁已设置");
            return "请勿重复提交信息,分布式锁已设置";
        }
        // 处理请求
        return joinPoint.proceed(args);
    }

    /**
     * 跟据所有参数获取redisKey
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        Object[] args = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        sb.append(className).append(":").append(methodName);
        for (Object arg : args) {
            sb.append(":").append(arg.toString());
        }
        return sb.toString();
    }
}
